﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using ThomasGIS.Coordinates;
using ThomasGIS.Geometries;
using ThomasGIS.Helpers;
using ThomasGIS.TrajectoryPackage.QuickIO;
using ThomasGIS.Vector;

namespace ThomasGIS.TrajectoryPackage
{
    public class TrajectorySet : ITrajectorySet
    {
        public List<Trajectory> TrajectoryList { get; } = new List<Trajectory>();

        public int TrajectoryNumber => TrajectoryList.Count;

        public TrajectorySet()
        {
            
        }

        public TrajectorySet(List<Trajectory> trajectoryList)
        {
            this.TrajectoryList.AddRange(trajectoryList);
        }

        public IShapefile ExportToShapefile(CoordinateBase targetCoordinate)
        {
            IShapefile shapefile = VectorFactory.CreateShapefile(ESRIShapeType.Polyline);

            shapefile.AddField("ID", DBFFieldType.Char, 50, 0);
            shapefile.AddField("Length", DBFFieldType.Number, 20, 5);

            foreach (Trajectory trajectory in TrajectoryList)
            {
                Polyline newPolyline = new Polyline(trajectory.GetPointEnumerable());
                Dictionary<string, object> values = new Dictionary<string, object>();
                values.Add("ID", trajectory.TaxiID);
                values.Add("Length", trajectory.Length);
                shapefile.AddFeature(newPolyline, values);
            }

            // 如果轨迹坐标系与其不一致，需要坐标转换
            // 暂时还没做 TODO
            shapefile.SetCoordinateRef(targetCoordinate);

            return shapefile;
        }

        public TrajectorySet(string filePath, ITrajectorySetCreator creator, CoordinateBase coordinateSystem, bool preprocessFlag = false, bool trajectorySplitFlag = false)
        {
            List<GnssPoint> originGnssPointList = new List<GnssPoint>();
            Dictionary<string, List<GnssPoint>> originTrajectorySet = new Dictionary<string, List<GnssPoint>>();

            using (StreamReader sr = new StreamReader(new FileStream(filePath, FileMode.Open)))
            {
                while (!sr.EndOfStream)
                {
                    string line = sr.ReadLine();
                    GnssPoint newPoint = creator.Reader(line);
                    if (!originTrajectorySet.ContainsKey(newPoint.ID))
                    {
                        originTrajectorySet.Add(newPoint.ID, new List<GnssPoint>());
                    }
                    originGnssPointList.Add(newPoint);
                }
            }

            
            foreach (GnssPoint gnssPoint in originGnssPointList)
            {
                originTrajectorySet[gnssPoint.ID].Add(gnssPoint);
            }

            foreach(KeyValuePair<string, List<GnssPoint>> pair in originTrajectorySet)
            {
                // 轨迹对象的轨迹点数量 < 2 则无法生成
                if (pair.Value.Count < 2) continue;

                Trajectory newTrajectory = new Trajectory(pair.Key, coordinateSystem, pair.Value);
                if (preprocessFlag)
                {
                    creator.PointFilter(newTrajectory);
                }

                if (newTrajectory.PointNumber < 2) continue;

                if (trajectorySplitFlag)
                {
                    IEnumerable<Trajectory> trajectories = creator.TrajectorySpliter(newTrajectory);
                    if (trajectories.Count() >= 1)
                    {
                        for (int i = 0; i < trajectories.Count(); i++)
                        {
                            if (trajectories.ElementAt(i).PointNumber < 2) continue;
                            if (trajectories.ElementAt(i).Length == 0) continue;
                            this.TrajectoryList.Add(trajectories.ElementAt(i));
                        }
                    }
                }
                else
                {
                    this.TrajectoryList.Add(newTrajectory);
                }
            }
        }

        public bool AddTrajectory(Trajectory trajectory)
        {
            this.TrajectoryList.Add(trajectory);
            return true;
        }

        public bool RemoveTrajectory(int index)
        {
            this.TrajectoryList.RemoveAt(index);
            return true;
        }

        public bool SortByLength()
        {
            this.TrajectoryList.Sort((t1, t2) =>
            {
                return t2.Length.CompareTo(t1.Length);
            });

            return true;
        }

        public IEnumerable<Trajectory> LengthLongerThan(double length)
        {
            return this.TrajectoryList.Where(trajectory => trajectory.Length >= length);
        }

        public IEnumerable<Trajectory> LengthShorterThan(double length)
        {
            return this.TrajectoryList.Where(trajectory => trajectory.Length <= length);
        }

        // 轨迹数据集在timestamp处的轨迹点快照
        public IEnumerable<GnssPoint> SnapShotByTime(int timeStamp)
        {
            List<GnssPoint> snapShotList = new List<GnssPoint>();

            foreach (Trajectory trajectory in TrajectoryList)
            {
                IEnumerable<GnssPoint> trajectoryPoints = trajectory.GetPointEnumerable();
                List<GnssPoint> pointList = trajectoryPoints.ToList();
                for (int i = 0; i < pointList.Count - 1; i++)
                {
                    GnssPoint startPoint = pointList[i];
                    GnssPoint endPoint = pointList[i + 1];

                    if (startPoint.Timestamp == endPoint.Timestamp) continue;

                    if (timeStamp >= startPoint.Timestamp && timeStamp <= endPoint.Timestamp)
                    {
                        double timePercent = (timeStamp - startPoint.Timestamp) / (endPoint.Timestamp - startPoint.Timestamp);
                        double snapPointX = startPoint.X + timePercent * (endPoint.X - startPoint.X);
                        double snapPointY = startPoint.Y + timePercent * (endPoint.Y - startPoint.Y);
                        double distance, angle;
                        if (trajectory.TrajectoryCoordinate.GetCoordinateType() == CoordinateType.Geographic)
                        {
                            distance = DistanceCalculator.SpatialDistanceGeo(startPoint, endPoint);
                            angle = DistanceCalculator.DirectionAngleGeo(startPoint, endPoint);
                        }
                        else
                        {
                            distance = DistanceCalculator.SpatialDistance(startPoint, endPoint);
                            angle = DistanceCalculator.SpatialDistance(startPoint, endPoint);
                        }

                        double speed = distance / (endPoint.Timestamp - startPoint.Timestamp);

                        snapShotList.Add(new GnssPoint(trajectory.TaxiID, snapPointX, snapPointY, timeStamp, angle, speed, startPoint.ExtraInformation));
                    }
                }
            }

            return snapShotList;
        }

        public BoundaryBox CalculateBoundaryBox()
        {
            double XMin = Double.MaxValue;
            double XMax = Double.MinValue;
            double YMin = Double.MaxValue;
            double YMax = Double.MinValue;

            foreach (Trajectory oneTrajectory in TrajectoryList)
            {
                for (int i = 0; i < oneTrajectory.PointNumber; i++)
                {
                    GnssPoint gnssPoint = oneTrajectory.GetPointByIndex(i);
                    if (gnssPoint.X < XMin)
                    {
                        XMin = gnssPoint.X;
                    }

                    if (gnssPoint.X > XMax)
                    {
                        XMax = gnssPoint.X;
                    }

                    if (gnssPoint.Y < YMin)
                    {
                        YMin = gnssPoint.Y;
                    }

                    if (gnssPoint.Y > YMax)
                    {
                        YMax = gnssPoint.Y;
                    }
                }
            }

            return new BoundaryBox(XMin, YMin, XMax, YMax);
        }

        public IEnumerable<Trajectory> OriginRegionFilter(Polygon polygon)
        {
            List<Trajectory> result = new List<Trajectory>();

            foreach(Trajectory trajectory in this.TrajectoryList)
            {
                 bool iscontainori = TopoCalculator.Contains(polygon, trajectory.GetPointByIndex(0));

                 bool iscontaindest = TopoCalculator.Contains(polygon, trajectory.GetPointByIndex(-1));

                 if (iscontainori && !iscontaindest)
                 {
                     result.Add(trajectory);
                 }

            }

            return result;
        }

        public IEnumerable<Trajectory> DestinationRegionFilter(Polygon polygon)
        {
            List<Trajectory> result = new List<Trajectory>();

            foreach (Trajectory trajectory in this.TrajectoryList)
            {
                bool isContainOri = TopoCalculator.Contains(polygon, trajectory.GetPointByIndex(0));

                bool isContainDest = TopoCalculator.Contains(polygon, trajectory.GetPointByIndex(-1));

                if (!isContainOri && isContainDest)
                {
                    result.Add(trajectory);
                }
            }

            return result;
        }

        public bool ExportToText(string filepath)
        {
            int trajectoryCount = this.TrajectoryNumber;
            using (StreamWriter sw = new StreamWriter(new FileStream(filepath, FileMode.Create)))
            {
                foreach (Trajectory oneTrajectory in this.TrajectoryList)
                {
                    foreach (GnssPoint gnssPoint in oneTrajectory.GetPointEnumerable())
                    {
                        string pointInfo = String.Format("{0},{1},{2},{3},{4},{5}", gnssPoint.ID, gnssPoint.X, gnssPoint.Y, gnssPoint.Timestamp, gnssPoint.Speed, gnssPoint.Direction);
                        foreach (string key in gnssPoint.ExtraInformation.Keys)
                        {
                            pointInfo += $",{ Convert.ToInt32(gnssPoint.ExtraInformation[key]) }";
                        }
                        sw.WriteLine(pointInfo);
                    }
                }
                sw.Close();
            }
            return true;
        }
    }
}
